import db
import os
import ctypes
import base64
import math
import time
import requests
from flask import Flask, render_template, request, jsonify, send_file
from werkzeug.utils import secure_filename
from io import BytesIO
from dotenv import load_dotenv
from random import randint

load_dotenv()


def get_env_int(key, default):
    try:
        return int(os.environ.get(key, default))
    except Exception:
        return default


def get_env_float(key, default):
    try:
        return float(os.environ.get(key, default))
    except Exception:
        return default


app = Flask(__name__)

DLL_PATH = os.path.join(os.path.dirname(__file__), "bin", "uareu4500.dll")
fingerprint_lib = ctypes.CDLL(DLL_PATH, winmode=0)
fingerprint_lib.python_read_fingerprint_and_get_base64_string.restype = ctypes.c_char_p
fingerprint_lib.python_compare_base64_string_with_finger.argtypes = [ctypes.c_char_p]
fingerprint_lib.python_compare_base64_string_with_finger.restype = ctypes.c_int


def get_finger_reading_as_base64():
    ptr = fingerprint_lib.python_read_fingerprint_and_get_base64_string()
    if not ptr:
        raise RuntimeError("Scan failed or DLL returned NULL")
    return ctypes.string_at(ptr).decode("utf-8")


def compare_base64_string_with_finger_reading(base64_string):
    res = fingerprint_lib.python_compare_base64_string_with_finger(
        base64_string.encode("utf-8")
    )
    return bool(res)


def compare_fmds_python(fmd1: bytes, fmd2: bytes):
    fmd1_size = ctypes.c_uint(len(fmd1))
    fmd2_size = ctypes.c_uint(len(fmd2))
    fmd1_buf = (ctypes.c_ubyte * len(fmd1))(*fmd1)
    fmd2_buf = (ctypes.c_ubyte * len(fmd2))(*fmd2)
    score = ctypes.c_uint(0)

    res = fingerprint_lib.compare_fmds(
        fmd1_size, fmd1_buf, fmd2_size, fmd2_buf, ctypes.byref(score)
    )
    return res, score.value


def reset_device():
    global fingerprint_lib
    try:
        fingerprint_lib = None
        time.sleep(0.5)
        fingerprint_lib = ctypes.CDLL(DLL_PATH, winmode=0)
        fingerprint_lib.python_read_fingerprint_and_get_base64_string.restype = (
            ctypes.c_char_p
        )
        fingerprint_lib.python_compare_base64_string_with_finger.argtypes = [
            ctypes.c_char_p
        ]
        fingerprint_lib.python_compare_base64_string_with_finger.restype = ctypes.c_int
    except Exception as e:
        print("[Reset Device] Error:", e)
        raise


def send_telegram_notify(message):
    enabled = os.environ.get("TELEGRAM_NOTIFY_ENABLED", "0").strip()
    if enabled not in ["1", "true", "True", "yes", "YES"]:
        return True

    token = os.environ.get("TELEGRAM_TOKEN", "").strip()
    chat_id = os.environ.get("TELEGRAM_CHAT_ID", "").strip()
    # print(chat_id, token)
    if not token or not chat_id:
        return False

    url = f"https://api.telegram.org/bot{token}/sendMessage"
    data = {"chat_id": chat_id, "text": message, "parse_mode": "HTML"}
    try:
        resp = requests.post(url, data=data, timeout=8)
        resp.raise_for_status()
        print(f"[TELEGRAM] Sent to {chat_id} OK")
        return True
    except Exception as e:
        print(f"[TELEGRAM] Error: {e}")
        return False


def _user_row_to_dict(row):
    """
    รับได้ทั้ง dict (dictionary=True) และ tuple/list (cursor ปกติ)
    สำหรับ fetch_user_by_id() ที่คืน: (id, fullname, year, department, photo)
    """
    if isinstance(row, dict):
        # เผื่ออนาคตสลับไปใช้ dictionary=True
        return {
            "id": row.get("id"),
            "student_code": row.get("student_code"),  # อาจไม่มี ถ้า SELECT ไม่ดึงมา
            "fullname": row.get("fullname"),
            "year": row.get("year"),
            "department": row.get("department"),
            "photo": row.get("photo"),
        }
    # tuple/list ตามลำดับใน db.py
    return {
        "id": row[0] if len(row) > 0 else None,
        "student_code": None,  # fetch_user_by_id ไม่ได้ select มา
        "fullname": row[1] if len(row) > 1 else None,
        "year": row[2] if len(row) > 2 else None,
        "department": row[3] if len(row) > 3 else None,
        "photo": row[4] if len(row) > 4 else None,
    }


@app.route("/api/reset_device", methods=["POST"])
def api_reset_device():
    try:
        reset_device()
        return jsonify({"success": True, "msg": "Device reset สำเร็จ!"})
    except Exception as e:
        return jsonify({"success": False, "msg": str(e)})


@app.route("/")
def index():
    users = db.fetch_all_users()
    fp_score_threshold = get_env_int("FP_SCORE_THRESHOLD", 2000)
    return render_template(
        "index.html", users=users, FP_SCORE_THRESHOLD=fp_score_threshold
    )


# Session Section
@app.route("/session")
def session_page():
    fp_score_threshold = get_env_int("FP_SCORE_THRESHOLD", 2000)
    return render_template("session.html", FP_SCORE_THRESHOLD=fp_score_threshold)


@app.route("/history")
def history():
    return render_template("history.html")


@app.route("/api/search_sessions")
def api_search_sessions():
    q = request.args.get("q", "").strip()
    sessions = db.search_closed_sessions(q)
    return jsonify(sessions)


@app.route("/api/session_history/<int:session_id>")
def api_session_history(session_id):
    logs = db.get_verified_logs_by_session(session_id)
    # Encode photo base64 for each log
    for l in logs:
        if l.get("photo") and isinstance(l["photo"], bytes):
            l["photo"] = base64.b64encode(l["photo"]).decode("utf-8")
    return jsonify(logs)


@app.route("/api/session_status")
def api_session_status():
    sess = db.get_current_session()
    if not sess:
        return jsonify({"active": False})
    return jsonify(
        {
            "active": True,
            "session": {
                "id": sess["id"],
                "name": sess["name"],
                "start_time": str(sess["start_time"]),
                "status": sess["status"],
            },
        }
    )


@app.route("/api/open_session", methods=["POST"])
def api_open_session():
    if request.is_json:
        name = request.json.get("name")
    else:
        name = request.form.get("name")
    sess = db.start_session(name)
    send_telegram_notify(f"✅ เปิดเซสซั่นใหม่:\nชื่อ: {name}")
    return jsonify({"success": True, "session": sess})


@app.route("/api/close_session", methods=["POST"])
def api_close_session():
    sess = db.end_session()
    send_telegram_notify(f"❌ ปิดเซสซั่นปัจจุบัน")
    return jsonify({"success": True})


@app.route("/api/session_verify", methods=["POST"])
def api_session_verify():
    sess = db.get_current_session()
    if not sess:
        return jsonify({"success": False, "error": "ยังไม่ได้เปิด Session"})
    fmd_live_base64 = get_finger_reading_as_base64()
    fmd_live = base64.b64decode(fmd_live_base64)
    threshold = int(os.environ.get("FP_SCORE_THRESHOLD", 2000))
    min_percent = float(os.environ.get("FP_MATCH_PERCENT", 60.0)) / 100.0

    users = db.fetch_all_users_with_fmds()
    best_score, best_user, best_user_id = None, None, None
    for user in users:
        for _, fmd_bytes in user["samples"]:
            _, score = compare_fmds_python(fmd_live, fmd_bytes)
            if score is None:
                continue
            if best_score is None or score < best_score:
                best_score = score
                best_user = user
                best_user_id = user["id"]
        if best_score is not None and best_score < threshold:
            break

    if best_user and best_score < threshold:
        already = db.check_verified_in_session(sess["id"], best_user_id)
        if already:
            return jsonify({"success": False, "error": "ผู้ใช้นี้ verify ใน session นี้แล้ว"})
        db.add_verify_log(sess["id"], best_user_id, best_score, True)
        profile = {
            "id": best_user["id"],
            "student_code": best_user["student_code"],
            "fullname": best_user["fullname"],
            "department": best_user["department"],
            "photo": (
                base64.b64encode(best_user["photo"]).decode("utf-8")
                if best_user.get("photo")
                else ""
            ),
        }
        send_telegram_notify(
            f"👤 ยืนยันตัวตน:\nชื่อ-นามสกุล: {best_user['fullname']}\nรหัสนักเรียน: {best_user['student_code']}\nปี: {best_user['year']}\nสังกัด: {best_user['department']}"
        )
        return jsonify({"success": True, "score": best_score, "profile": profile})
    else:
        db.add_verify_log(
            sess["id"],
            None,
            best_score if best_score else 0,
            False,
            error="ไม่พบข้อมูลลายนิ้วมือ",
        )
        return jsonify({"success": False, "error": "ไม่พบข้อมูลลายนิ้วมือที่ตรงกันในระบบ"})


@app.route("/api/session_users_logs")
def api_session_users_logs():
    sess = db.get_current_session()
    if not sess:
        return jsonify({"users": []})
    users = db.get_session_users_logs(sess["id"])  # สมมติ return [{...}]
    for u in users:
        if u.get("photo") and isinstance(u["photo"], bytes):
            u["photo"] = base64.b64encode(u["photo"]).decode("utf-8")
        else:
            u["photo"] = ""
    return jsonify({"users": users})


@app.route("/user_profile/<int:user_id>")
def user_profile(user_id):
    user_row = db.fetch_user_by_id(user_id)
    if not user_row:
        return jsonify({"success": False, "error": "USER_NOT_FOUND"}), 404

    u = _user_row_to_dict(user_row)

    # ดึง student_code เพิ่ม เพราะ fetch_user_by_id ไม่ได้ select มา
    code = db.fetch_user_code(user_id)

    photo_b64 = base64.b64encode(u["photo"]).decode("utf-8") if u["photo"] else ""

    return jsonify(
        {
            "success": True,
            "profile": {
                "id": u["id"],
                "student_code": code,
                "fullname": u["fullname"],
                "year": u["year"],
                "department": u["department"],
                "photo": photo_b64,
            },
        }
    )


@app.route("/user_photo/<int:user_id>")
def user_photo(user_id):
    user_row = db.fetch_user_by_id(user_id)
    if not user_row:
        return "", 404
    u = _user_row_to_dict(user_row)
    if u["photo"]:
        return send_file(BytesIO(u["photo"]), mimetype="image/jpeg")
    return "", 404


@app.route("/enroll", methods=["POST"])
def enroll():
    student_code = request.form.get("student_code")
    fullname = request.form.get("fullname")
    year = request.form.get("year")
    department = request.form.get("department")
    file = request.files.get("photo")
    if not (student_code and fullname and year and department and file):
        return jsonify({"success": False, "error": "กรุณากรอกข้อมูลและเลือกรูป"})
    photo_bytes = file.read()
    try:
        user_id = db.insert_user(student_code, fullname, year, department, photo_bytes)
    except ValueError as e:
        return jsonify({"success": False, "error": str(e)})
    except Exception as e:
        return jsonify({"success": False, "error": "เกิดข้อผิดพลาด: " + str(e)})

    for sample_no in range(1, 6):
        fmd_base64 = get_finger_reading_as_base64()
        fmd_bytes = base64.b64decode(fmd_base64)
        db.insert_fingerprint(user_id, sample_no, fmd_bytes)
    send_telegram_notify(
        f"✅ เพิ่มข้อมูลใหม่:\nชื่อ-นามสกุล: {fullname}\nรหัสนักเรียน: {student_code}\nปี: {year}\nสังกัด: {department}"
    )
    return jsonify({"success": True, "user_id": user_id})


@app.route("/verify_by_code", methods=["POST"])
def verify_by_code():
    fmd_live_base64 = get_finger_reading_as_base64()
    fmd_live = base64.b64decode(fmd_live_base64)

    threshold = get_env_int("FP_SCORE_THRESHOLD", 2000)
    users_per_batch = get_env_int("FP_COMPARE_BATCH_SIZE", 100)
    min_percent = get_env_float("FP_MATCH_PERCENT", 60.0) / 100.0

    student_code = request.json.get("student_code")
    best_score = None
    best_user = None
    best_user_id = None

    def get_one_sample(samples):
        # สุ่ม sample หรือเลือก sample แรก (ได้หมด)
        return samples[randint(0, len(samples) - 1)] if samples else None

    if student_code:
        user = db.fetch_user_by_code(student_code)
        if not user:
            return jsonify({"success": False, "error": "ไม่พบรหัสนักเรียนนี้"})
        user_id, code, fullname, year, department, photo = user
        samples = db.fetch_fmds_by_user(user_id)
        if not samples:
            return jsonify({"success": False, "error": "ยังไม่มีข้อมูลลายนิ้วมือในระบบ"})

        scores = []
        for sample_no, fmd_bytes in samples:
            _, score = compare_fmds_python(fmd_live, fmd_bytes)
            scores.append(score)

        matched_count = sum([s < threshold for s in scores])
        if len(scores) > 0 and matched_count / len(scores) >= min_percent:
            # Return profile (photo เป็น base64 เสมอ)
            profile = {
                "id": user_id,
                "student_code": code,
                "fullname": fullname,
                "year": year,
                "department": department,
                "photo": base64.b64encode(photo).decode("utf-8") if photo else "",
            }
            return jsonify({"success": True, "score": min(scores), "profile": profile})
        else:
            return jsonify({"success": False, "error": "ไม่ตรงกัน"})

    else:
        users = db.fetch_all_users_with_fmds()  # dict: id, ... , samples, photo
        if not users:
            return jsonify({"success": False, "error": "ยังไม่มีข้อมูลในระบบ"})

        total_users = len(users)
        i = 0
        while i < total_users:
            batch = users[i : i + users_per_batch]
            for user in batch:
                samples = user.get("samples", [])
                if not samples:
                    continue
                sample_no, fmd_bytes = get_one_sample(samples)
                _, score = compare_fmds_python(fmd_live, fmd_bytes)
                if score is None:
                    continue
                if best_score is None or score < best_score:
                    best_score = score
                    best_user = user
                    best_user_id = user["id"]
                if score < threshold:
                    break
            if best_score is not None and best_score < threshold:
                break
            i += users_per_batch

        if best_user and best_score < threshold:
            # ดึง 5 sample ของ Best User (รอบ 2)
            fmd_samples = db.fetch_fmds_by_user(best_user_id)
            scores = []
            for sample_no, fmd_bytes in fmd_samples:
                _, score = compare_fmds_python(fmd_live, fmd_bytes)
                scores.append(score)
            matched_count = sum([s < threshold for s in scores])
            if len(scores) == 0:
                return jsonify({"success": False, "error": "พบผู้ใช้แต่ไม่มีข้อมูลลายนิ้วมือ"})
            if matched_count / len(scores) >= min_percent:
                profile = {
                    "id": best_user["id"],
                    "student_code": best_user["student_code"],
                    "fullname": best_user["fullname"],
                    "year": best_user["year"],
                    "department": best_user["department"],
                    "photo": (
                        base64.b64encode(best_user["photo"]).decode("utf-8")
                        if best_user.get("photo")
                        else ""
                    ),
                }
                return jsonify(
                    {"success": True, "score": min(scores), "profile": profile}
                )
            else:
                return jsonify({"success": False, "error": "ไม่ตรงกัน"})
        else:
            return jsonify({"success": False, "error": "ไม่พบข้อมูลลายนิ้วมือที่ตรงกันในระบบ"})


@app.route("/all_users_json")
def all_users_json():
    users = []
    for u in db.fetch_all_users_with_photo():
        users.append(
            {
                "id": u[0],
                "student_code": u[1],
                "fullname": u[2],
                "year": u[3],
                "department": u[4],
                "photo": base64.b64encode(u[5]).decode() if u[5] else "",
            }
        )
    return jsonify(users)


@app.route("/delete_user", methods=["POST"])
def delete_user_route():
    user_id = request.json.get("user_id")
    pin = request.json.get("pin")
    real_pin = os.environ.get("DELETE_PIN")

    if pin != real_pin:
        return jsonify({"success": False, "error": "PIN ไม่ถูกต้อง"})

    user_row = db.fetch_user_by_id(user_id)
    if not user_row:
        return jsonify({"success": False, "error": "ไม่พบผู้ใช้"}), 404

    u = _user_row_to_dict(user_row)
    # fetch student_code เพิ่ม เพราะ fetch_user_by_id เดิมไม่ได้ select มา
    if not u.get("student_code"):
        u["student_code"] = db.fetch_user_code(user_id)

    db.delete_user(user_id)

    send_telegram_notify(
        f"❌ ลบข้อมูลออก:\n"
        f"ชื่อ-นามสกุล: {u.get('fullname')}\n"
        f"รหัสนักเรียน: {u.get('student_code')}\n"
        f"ปี: {u.get('year')}\n"
        f"สังกัด: {u.get('department')}"
    )
    return jsonify({"success": True})


if __name__ == "__main__":
    app.run(debug=True)
